/*
	YoghurtGum - 2D Game Engine

	Copyright 2009-2011 Quinten Lansu
	
	Licensed under the Apache License, Version 2.0 (the "License"); you may not 
	use this file except in compliance with the License. You may obtain a copy 
	of the License at 
		
		http://www.apache.org/licenses/LICENSE-2.0 
		
	Unless required by applicable law or agreed to in writing, software 
	distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
	WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
	License for the specific language governing permissions and limitations 
	under the License. 
*/

#ifndef _YGGENERALMATH_H_
#define _YGGENERALMATH_H_

#include "YGSettings.h"
#include "YGPlatform.h"

#include <math.h>
#if (YG_OS_TYPE != YG_OS_TYPE_ANDROID)
	#include <ctime>
#else
	#include <time.h>
#endif
#include <stdlib.h>

namespace YoghurtGum
{

	namespace Math
	{
	
		// ====================================
		// Constants
		// ====================================

		// hmm... pi...

		static const float pi = 3.141592653f;
		static const float pi_radians = pi / 180.f;
		static const float pi_degrees = 180.f / pi;
		static const float pi_half = pi / 2;
		static const float pi_two = pi * 2;
		static const float pi_rcp = 1 / pi;
		static const float pi_rcp_two = 1 / (pi_two);

		// ====================================
		// Basic functions
		// ====================================

		inline float Sqrt(float a_X)
		{

			return (sqrtf(a_X));

/*#if (YG_OS_TYPE == YG_OS_TYPE_WINDOWSMOBILE)

			return (sqrtf(a_X));

#elif (YG_OS_TYPE == YG_OS_TYPE_BADA)

			return (sqrtf(a_X));

#endif*/

		}

		/*
			Returns the fractional part of a_X.
		*/
		inline float Frac(float a_X)
		{
			return (a_X - (float)(int)(a_X));

			//return ((int)(a_X * fp_amount) & 0xFFFF) * fp_amount_inv;
		}

		/*
			Returns the inverse fractional part (1 - 0.55 = 0.45) of a_X.
		*/
		inline float InvFrac(float a_X)
		{
			return (1.f - (a_X - (float)(int)(a_X)));

			//return (0xFFFF - ((int)(a_X * fp_amount) & 0xFFFF)) * fp_amount_inv;
		}

		/*
			Returns an integer with the number rounded to the nearest integer.
		*/
		inline int Round(float a_X) 
		{ 
			return ((int)(a_X + 0.5f)); 
		} 

		/*
			Returns an integer with the number rounded _down_ to the nearest integer.
		*/
		inline int Floor(float a_Value) 
		{ 
			return ((int)a_Value); 
		}

		/*
			Returns an integer with the number rounded _up_ to the nearest integer.
		*/
		inline int Ceil(float a_Value) 
		{ 
			return (((int)(a_Value + 1))); 
		}

		/*
			Returns the absolute value of a number
		*/
		inline float Abs(float a_Value)
		{

#if (YG_OS_TYPE == YG_OS_TYPE_ANDROID)

			return (fabsf(a_Value));

#elif (YG_OS_TYPE == YG_OS_TYPE_WINDOWSMOBILE)

			return (fabsf(a_Value));

#elif (YG_OS_TYPE == YG_OS_TYPE_BADA)

			return (fabsf(a_Value));
#endif

		}

		// ====================================
		// Angle math
		// ====================================

		/*
			Converts degrees (0 - 360) to radians (0 - 2 pi).
		*/
		inline float DegToRad(float a_Degrees) { return (a_Degrees * pi_radians); }

		/* 
			Converts radians (0 - 2 pi) to degrees (0 - 360). 
		*/
		inline float RadToDeg(float a_Radians) { return (a_Radians * pi_degrees); }

		/*
			Returns the sine of an amount of radians.
		*/
		inline float Sin(float a_Radians) 
		{ 

#if (YG_OS_TYPE == YG_OS_TYPE_ANDROID)

			return sinf(a_Radians);

#else

			//const float A = pi_rcp_two;
			const float B = -16.0f;
			const float C = 8.0f;

			// scale angle for easy argument reduction
			a_Radians *= pi_rcp_two;

			if (Abs(a_Radians) >= 0.5f) 
			{
				// argument reduction
				a_Radians = a_Radians - Ceil(a_Radians + 0.5f) + 1.0f;
			}

			const float y = (B * a_Radians * Abs(a_Radians)) + (C * a_Radians);
			return 0.2215f * (y * Abs(y) - y) + y;

#endif

		}

		/*
			Returns the sine of a number of degrees.
		*/
		inline float SinDeg(float a_Degrees) { return Sin(a_Degrees * pi_radians); }

		/*
			Returns the cosine of an amount of radians.
		*/
		inline float Cos(float a_Radians) 
		{ 

#if (YG_OS_TYPE == YG_OS_TYPE_ANDROID)

			return (cosf(a_Radians));

#else

			return (Sin(a_Radians + pi_half)); 

#endif

		}

		/*
			Returns the cosine of a number of degrees.
		*/
		inline float CosDeg(float a_Degrees) 
		{ 

#if (YG_OS_TYPE == YG_OS_TYPE_ANDROID)

			return (cosf(a_Degrees * pi_radians));

#else

			return Cos(a_Degrees * pi_radians); 

#endif

		}

		// ====================================
		// Randomness
		// ====================================

#if (YG_OS_TYPE == YG_OS_TYPE_WINDOWSMOBILE || YG_OS_TYPE == YG_OS_TYPE_BADA || YG_OS_TYPE == YG_OS_TYPE_ANDROID)

		const float inv_rand_max = 1 / (float)RAND_MAX;

#endif

		/*
			Seeds the random number generator. 
			If a seed of 0 is specified, the function inputs the current time in milliseconds as the seed.
		*/
		inline void Seed(unsigned int a_Seed = 0)
		{

#if (YG_OS_TYPE == YG_OS_TYPE_WINDOWSMOBILE || YG_OS_TYPE == YG_OS_TYPE_BADA)

			unsigned int seed = (a_Seed == 0) ? (unsigned int)(time(0)) : a_Seed;
			srand(seed);

#elif (YG_OS_TYPE == YG_OS_TYPE_ANDROID)

			unsigned int seed = (a_Seed == 0) ? (unsigned int)(time(0)) : a_Seed;
			srand(seed);

#endif

		}

		/*
			Outputs a random number between 0 and 1.
		*/
		inline float Random()
		{

#if (YG_OS_TYPE == YG_OS_TYPE_WINDOWSMOBILE || YG_OS_TYPE == YG_OS_TYPE_BADA)

			return ((float)(rand()) * inv_rand_max);

#elif (YG_OS_TYPE == YG_OS_TYPE_ANDROID)

			return ((float)(rand()) * inv_rand_max);

#endif

		}

		/*
			Outputs a random number between 0 and a_Max.
		*/
		inline float Random(float a_Max)
		{

#if (YG_OS_TYPE == YG_OS_TYPE_WINDOWSMOBILE || YG_OS_TYPE == YG_OS_TYPE_BADA)

			return ((float)(rand()) * inv_rand_max) * a_Max;

#elif (YG_OS_TYPE == YG_OS_TYPE_ANDROID)

			return ((float)(rand()) * inv_rand_max) * a_Max;

#endif
		}

		/*
			Outputs a random number between a_Min and a_Max.
		*/
		inline float Random(float a_Min, float a_Max)
		{

#if (YG_OS_TYPE == YG_OS_TYPE_WINDOWSMOBILE || YG_OS_TYPE == YG_OS_TYPE_BADA)

			return (a_Min + ((float)(rand()) * inv_rand_max) * a_Max);

#elif (YG_OS_TYPE == YG_OS_TYPE_ANDROID)

			return (a_Min + ((float)(rand()) * inv_rand_max) * a_Max);

#endif

		}

		// ====================================
		// Binary
		// ====================================

		inline uint32 BytesToKilobytes(uint32 a_Amount)
		{
			return (a_Amount >> 10);
		}

		inline uint32 BytesToMegabytes(uint32 a_Amount)
		{
			return (a_Amount >> 20);
		}

		inline uint32 KilobytesToBytes(uint32 a_Amount)
		{
			return (a_Amount << 10);
		}

		inline uint32 MegabytesToBytes(uint32 a_Amount)
		{
			return (a_Amount << 20);
		}

	}; // namespace Math
	
};

#endif
